#include <strmif.h>
#include "cfactory.hpp"
#include "comreg.hpp"
#include <cassert>
#include <comdef.h>
#include <uuids.h>

HMODULE g_hModule;
static ULONG s_cLock;

_COM_SMARTPTR_TYPEDEF(IFilterMapper2, __uuidof(IFilterMapper2));    

namespace MkvSource
{
    extern const CLSID CLSID_MkvSource;

    HRESULT CreateInstance(
            IClassFactory*,
            IUnknown*, 
            const IID&, 
            void**);

}  //end namespace MkvSource


static MJH::CFactory s_factory(&s_cLock, &MkvSource::CreateInstance);


BOOL APIENTRY DllMain(
    HINSTANCE hModule, 
    DWORD  dwReason, 
    LPVOID)
{
   switch (dwReason)
   {
       case DLL_PROCESS_ATTACH:
        {
            g_hModule = hModule;
           break;
        }
       case DLL_THREAD_ATTACH:
       case DLL_THREAD_DETACH:
       case DLL_PROCESS_DETACH:
       default:
          break;
   }
   
    return TRUE;
}



STDAPI DllCanUnloadNow()
{
    return s_cLock ? S_FALSE : S_OK;
}


STDAPI DllGetClassObject(
    const CLSID& clsid,
    const IID& iid,
    void** ppv)
{
    if (clsid == MkvSource::CLSID_MkvSource)
        return s_factory.QueryInterface(iid, ppv);

    return CLASS_E_CLASSNOTAVAILABLE;
}


STDAPI DllUnregisterServer()
{
    const IFilterMapper2Ptr pMapper(CLSID_FilterMapper2);
    assert(bool(pMapper));

    HRESULT hr = pMapper->UnregisterFilter(
                    &CLSID_LegacyAmFilterCategory,
                    0,
                    MkvSource::CLSID_MkvSource);
                    
    //assert(SUCCEEDED(hr));

    hr = ComReg::UnRegisterCustomFileType(L".mkv", MkvSource::CLSID_MkvSource);                
    assert(SUCCEEDED(hr));

    hr = ComReg::UnRegisterCoclass(MkvSource::CLSID_MkvSource);

    return SUCCEEDED(hr) ? S_OK : S_FALSE;
}


STDAPI DllRegisterServer()
{
    std::wstring filename_;

    HRESULT hr = ComReg::ComRegGetModuleFileName(g_hModule, filename_);
    assert(SUCCEEDED(hr));
    assert(!filename_.empty());
    
    const wchar_t* const filename = filename_.c_str();

#if _DEBUG    
    const wchar_t friendlyname[] = L"MJH MKV Source (Debug)";
#else
    const wchar_t friendlyname[] = L"MJH MKV Source";
#endif

    hr = DllUnregisterServer();
    assert(SUCCEEDED(hr));
    
    hr = ComReg::RegisterCoclass(
            MkvSource::CLSID_MkvSource,
            friendlyname,
            filename,
            L"MJH.MkvSource",
            L"MJH.MkvSource.1",
            false,  //not insertable
            false,  //not a control
            ComReg::kBoth,  //DShow filters must support "both"
            GUID_NULL,     //typelib
            0,    //no version specified
            0);   //no toolbox bitmap
            
    assert(SUCCEEDED(hr));
            
#if 1  
    //TODO: I thought a .reg file would be adequate to do this, 
    //but for some reason none of GraphEdt, GraphStudio, WMP, etc
    //load this source filter when asked to render a .MKV file.
    //I don't understand why not, because the registry entry is 
    //present.  (Does Win7 do something different?) Does using
    //regsvr32.exe to register a custom file type programmatically
    //sprinkle some pixie dust in the registry, that merging a .reg 
    //file from the command-line or shell does not?
    //
    //I would prefer not to take over a file extension, because this 
    //will clobber any existing registration (of a source filter for
    //the .MKV file extension).  Indeed I would prefer to not have to
    //register a custom file type at all.  (Otherwise what's the point
    //of calling FilterMappter->RegisterFile?)  But I cannot get the 
    //.MKV file to load unless I register this as a source filter, and 
    //only by registering the source filter here, in DllRegisterServer.  
    //I don't know why.
    
    hr = ComReg::RegisterCustomFileType(
            L".mkv",
            MkvSource::CLSID_MkvSource,
            GUID_NULL,
            GUID_NULL);
                
    assert(SUCCEEDED(hr));
#endif

    const IFilterMapper2Ptr pMapper(CLSID_FilterMapper2);
    assert(bool(pMapper));
    
#if 1
    enum { cPins = 1 };
    REGFILTERPINS pins[cPins];
    
    REGFILTERPINS& pin = pins[0];
    
    pin.strName = 0;              //obsolete
    pin.bRendered = FALSE;        //always FALSE for outpins
    pin.bOutput = TRUE;
    pin.bZero = FALSE;            //well, not really
    pin.bMany = TRUE;
    pin.clsConnectsToFilter = 0;  //obsolete
    pin.strConnectsToPin = 0;     //obsolete    
    pin.nMediaTypes = 0;
    pin.lpMediaType = 0;
#else
    enum { cPins = 2 };
    REGFILTERPINS pins[cPins];
    
    REGFILTERPINS& v = pins[0];
    
    const REGPINTYPES vt[] =
    {
        { &MEDIATYPE_Video, &MEDIASUBTYPE_NULL }
    };

    v.strName = 0;              //obsolete
    v.bRendered = FALSE;        //always FALSE for outpins
    v.bOutput = TRUE;
    v.bZero = FALSE;            //well, not really
    v.bMany = TRUE;
    v.clsConnectsToFilter = 0;  //obsolete
    v.strConnectsToPin = 0;     //obsolete    
    v.nMediaTypes = 1;
    v.lpMediaType = vt;

    REGFILTERPINS& a = pins[1];
    
    const REGPINTYPES at[] =
    {
        { &MEDIATYPE_Audio, &MEDIASUBTYPE_NULL }
    };

    a.strName = 0;              //obsolete
    a.bRendered = FALSE;        //always FALSE for outpins
    a.bOutput = TRUE;
    a.bZero = FALSE;            //well, not really
    a.bMany = TRUE;
    a.clsConnectsToFilter = 0;  //obsolete
    a.strConnectsToPin = 0;     //obsolete    
    a.nMediaTypes = 1;
    a.lpMediaType = at;
#endif

    //pin setup complete
    
    REGFILTER2 filter;
    
    filter.dwVersion = 1;
    filter.dwMerit = MERIT_NORMAL;
    filter.cPins = cPins;
    filter.rgPins = pins;
    
    hr = pMapper->RegisterFilter(
            MkvSource::CLSID_MkvSource,
            friendlyname,  //?
            0,
            &CLSID_LegacyAmFilterCategory,
            0,
            &filter);                       

    assert(SUCCEEDED(hr));
    
    return S_OK;
}
